package com.ruoyi.common.redis.component;

import com.ruoyi.common.redis.configure.FastJson2JsonRedisSerializer;
import com.ruoyi.common.redis.domain.RedisInfo;
import com.ruoyi.common.redis.service.RedisService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * redis 管理类
 */
@Slf4j
@Component
@RefreshScope
@AutoConfigureBefore(RedisService.class)
public class RedisTemplateManager {

    /**
     * 默认redis管理key
     */
    public static final String DEFAULT = "redis_default";
    private static final Integer maxActive = 32;
    private static final Integer maxIdle = 16;
    private static final Integer minIdle = 8;
    private static final Long TIMEOUT = 5_000L;
    private static final Long SHUTDOWN_TIMEOUT = 1_000L;

    @Value("${spring.redis.database:0}")
    private int database;

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.port}")
    private int port;

    /**
     * 所有redis
     * key： tenaantId
     */
    private final Map<String, RedisTemplate<Object, Object>> redisTemplateMap = new ConcurrentHashMap<>();
    private final Map<String, String> redisDataBaseMap = new ConcurrentHashMap<>();

    public RedisTemplate<Object, Object> getRedisTemplate(String tenantId) {
        return redisTemplateMap.getOrDefault(tenantId, redisTemplateMap.get(DEFAULT));
    }
    public String getRedisDataBase(String tenantId) {
        return redisDataBaseMap.getOrDefault(tenantId, redisDataBaseMap.get(DEFAULT));
    }

    /**
     * 因为启动类已排除redis 自动配置相关类
     */
    @PostConstruct
    private void initMasterRedisTemplate(){
        dynamicRedisTemplate(DEFAULT, RedisInfo.builder()
                .hostName(host)
                .port(port)
                .database(database)
                .password(password)
                .build());
    }

    /**
     * 刷新redis template
     */
    public synchronized void refreshRedisTemplate(Map<String,RedisInfo> redisInfoMap) {
        redisInfoMap = ObjectUtils.firstNonNull(redisInfoMap,new HashMap<>());
        redisInfoMap.forEach((tenantId,redisInfo)->
            dynamicRedisTemplate(tenantId,redisInfo)
        );
    }

    /**
     * 动态新增redisTemplate
     */
    public synchronized void dynamicRedisTemplate(String tenantId, RedisInfo redisInfo) {

        log.info("[dynamicRedisTemplate]starting....,params: tenantId:[{}],redisInfo:[{}]",tenantId,redisInfo);

        DynamicRedisTemplate<Object, Object> template = new DynamicRedisTemplate<>();
        template.setConnectionFactory(lettuceConnectionFactory(redisInfo.getHostName(),
                redisInfo.getPort(),
                redisInfo.getDatabase(),
                redisInfo.getPassword()));
        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);
        template.afterPropertiesSet();
        redisDataBaseMap.put(tenantId,redisInfo.getDatabase()+"");
        redisTemplateMap.put(tenantId, template);
        log.info("[dynamicRedisTemplate]end");
    }

    private synchronized void destory(String tenantId) {
        RedisTemplate<Object, Object> redisTemplate = redisTemplateMap.get(tenantId);
        if (redisTemplate != null
                && redisTemplate.getConnectionFactory() != null) {
            LettuceConnectionFactory factory = (LettuceConnectionFactory) redisTemplate.getConnectionFactory();
            factory.destroy();
        }
    }


    private LettuceConnectionFactory lettuceConnectionFactory(String host, Integer port, Integer database, String password) {

        GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig();
        genericObjectPoolConfig.setMaxIdle(maxIdle);
        genericObjectPoolConfig.setMinIdle(minIdle);
        genericObjectPoolConfig.setMaxTotal(maxActive);
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setDatabase(database);
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
        LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder()
                .commandTimeout(Duration.ofMillis(TIMEOUT))
                .shutdownTimeout(Duration.ofMillis(SHUTDOWN_TIMEOUT))
                .poolConfig(genericObjectPoolConfig)
                .build();
        LettuceConnectionFactory factory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig);
        factory.setShareNativeConnection(true);
        factory.setValidateConnection(false);
        factory.afterPropertiesSet();
        return factory;
    }

    public boolean containsTenantId(String tenantId) {
        return redisTemplateMap.containsKey(tenantId);
    }
}
